Clipping SVG output
In a message to the qt-interest mailing list, Jeremy Sanders asked if it was possible to clip polylines before sending them to a paint device. This was partly to overcome the limitations of Qt's SVG paint device, QSvgGenerator, which doesn't support clipping.
The following example code shows how to clip painter output when generating an SVG, using a QPainterPath as a stencil by passing the painter paths to be rendered to its QPainterPath.intersected() method and drawing the result. Note that this only works for pens with zero width.
1 import random, sys
2 from PyQt4.QtCore import QSize, Qt
3 from PyQt4.QtGui import *
4 from PyQt4.QtSvg import *
5
6 def randomColor():
7
8 red = 205 + random.random() * 50
9 green = 205 + random.random() * 50
10 blue = 205 + random.random() * 50
11 alpha = 91 + random.random() * 100
12
13 return QColor(red, green, blue, alpha)
14
15
16 if __name__ == "__main__":
17
18 if len(sys.argv) != 2:
19
20 sys.stderr.write("Usage: %s <output path>\n" % sys.argv[0])
21 sys.exit(1)
22
23 app = QApplication(sys.argv)
24
25 svg = QSvgGenerator()
26 svg.setFileName(sys.argv[1])
27 svg.setResolution(300)
28 svg.setSize(QSize(1024, 768))
29
30 random.seed()
31
32 p = QPainter()
33 p.begin(svg)
34
35 clipPath = QPainterPath()
36 clipPath.addRect(0, 0, 1024, 768)
37
38 gradient = QLinearGradient(0, 0, 1024, 768)
39 gradient.setColorAt(0, QColor(0, 64, 64))
40 gradient.setColorAt(1, QColor(0, 0, 64))
41 p.fillRect(0, 0, 1024, 768, QBrush(gradient))
42 p.setPen(Qt.NoPen)
43
44 font = QFont("FreeSans")
45
46 for i in range(100):
47
48 w = 10 + random.random() * 200.0
49 h = 0.5 * (1 + random.random()) * w
50 color = randomColor()
51 x = random.random() * 1024
52 y = random.random() * 768
53
54 rectPath = QPainterPath()
55 rectPath.addRoundedRect(x - w/2, y - h/2, w, h, 20, 20)
56
57 p.setBrush(color)
58 p.drawPath(clipPath.intersected(rectPath))
59
60 font.setPixelSize(min(w/2, h/2))
61 metrics = QFontMetrics(font, svg)
62 text = chr(random.randrange(97, 123))
63 rect = metrics.boundingRect(x - w/2, y - h/2, w, h, Qt.AlignCenter, text)
64 textPath = QPainterPath()
65 textPath.addText(x - rect.width()/2, y + rect.height()/2 - metrics.descent(), font, text)
66
67 p.setBrush(QColor(0, 0, 0, color.alpha()))
68 p.drawPath(clipPath.intersected(textPath))
69
70 p.end()
71
72 sys.exit()
One way to deal with non-zero-width pens is to use QPainterPathStroker. This creates an outline of a painter path, converting pen strokes to fillable paths. We can use this to create paths which we can then effectively clip using the stencil path.
1 import random, sys
2 from PyQt4.QtCore import QSize, Qt
3 from PyQt4.QtGui import *
4 from PyQt4.QtSvg import *
5
6 def randomColor():
7
8 red = 205 + random.random() * 50
9 green = 205 + random.random() * 50
10 blue = 205 + random.random() * 50
11 alpha = 91 + random.random() * 100
12
13 return QColor(red, green, blue, alpha)
14
15
16 if __name__ == "__main__":
17
18 if len(sys.argv) != 2:
19
20 sys.stderr.write("Usage: %s <output path>\n" % sys.argv[0])
21 sys.exit(1)
22
23 app = QApplication(sys.argv)
24
25 svg = QSvgGenerator()
26 svg.setFileName(sys.argv[1])
27 svg.setResolution(300)
28 svg.setSize(QSize(1024, 768))
29
30 random.seed()
31
32 p = QPainter()
33 p.begin(svg)
34
35 clipPath = QPainterPath()
36 clipPath.addRect(0, 0, 1024, 768)
37
38 stroker = QPainterPathStroker()
39 stroker.setWidth(16)
40 stroker.setDashPattern([0.1, 1.5])
41
42 gradient = QLinearGradient(0, 0, 1024, 768)
43 gradient.setColorAt(0, QColor(0, 64, 64))
44 gradient.setColorAt(1, QColor(0, 0, 64))
45 p.fillRect(0, 0, 1024, 768, QBrush(gradient))
46 p.setPen(Qt.NoPen)
47
48 font = QFont("FreeSans")
49 strokeColor = QColor(0, 127, 255)
50
51 for i in range(100):
52
53 w = 10 + random.random() * 200.0
54 h = 0.5 * (1 + random.random()) * w
55 color = randomColor()
56 x = random.random() * 1024
57 y = random.random() * 768
58
59 rectPath = QPainterPath()
60 rectPath.addRoundedRect(x - w/2, y - h/2, w, h, 20, 20)
61
62 strokedPath = stroker.createStroke(rectPath)
63
64 # Draw the background of the rounded rectangle.
65 p.setBrush(color)
66 p.drawPath(clipPath.intersected(rectPath))
67 # Draw the outline of the rounded rectangle.
68 p.setBrush(strokeColor)
69 p.drawPath(clipPath.intersected(strokedPath))
70
71 font.setPixelSize(min(w/2, h/2))
72 metrics = QFontMetrics(font, svg)
73 text = chr(random.randrange(97, 123))
74 rect = metrics.boundingRect(x - w/2, y - h/2, w, h, Qt.AlignCenter, text)
75 textPath = QPainterPath()
76 textPath.addText(x - rect.width()/2, y + rect.height()/2 - metrics.descent(), font, text)
77
78 p.setBrush(QColor(0, 0, 0, color.alpha()))
79 p.drawPath(clipPath.intersected(textPath))
80
81 p.end()
82
83 sys.exit()